package main

import (
	"encoding/binary"
	"flag"
	"fmt"
	gogocache "gitee.com/Gogo-gitee/gogo-cache"
	"gitee.com/Gogo-gitee/gogo-cache/log"
	pb "gitee.com/Gogo-gitee/gogo-cache/protocol"
	"gitee.com/Gogo-gitee/registry"
	"github.com/panjf2000/gnet/pool/goroutine"

	"github.com/panjf2000/gnet"
	"google.golang.org/protobuf/proto"

	"net"
	"strings"
	"time"
)

type CacheServer struct {
	*gnet.EventServer
	addr       string
	multicore  bool
	async      bool
	cache      *gogocache.CacheGroup
	codec      gnet.ICodec
	workerPool *goroutine.Pool
}

// OnInitComplete 初始化时，向注册中心注册
func (cs *CacheServer) OnInitComplete(srv gnet.Server) (action gnet.Action) {

	log.Debugf("Server is listening on %s (multi-cores: %t, loops: %d)\n",
		srv.Addr.String(), srv.Multicore, srv.NumEventLoop)

	zooKeeperRegistry := registry.ZooKeeperRegistry{Host: []string{registryAddress}}

	err := zooKeeperRegistry.Register(registryPath, cs.addr[6:], nil)
	if err != nil {
		log.Fatal("register failed")
		return
	}
	log.Debugf("register %s successfully", cs.addr[6:])
	return
}
func (cs *CacheServer) exec(r *pb.Request) *pb.Response {

	var rsp pb.Response

	if r.GetRequest != nil {
		rsp.Key = r.GetRequest.Key
		log.Debugf("exec get request")
		byteView, err := cs.cache.Get(r.GetRequest.Key)
		if err != nil {
			rsp.RetCode = 2
			rsp.RetMessage = err.Error()
			return &rsp
		}
		rsp.RetCode = 1
		rsp.ResponseType = 1
		rsp.Value = byteView.ByteSlice()
	} else {
		log.Debugf("exec set request")
		rsp.Key = r.SetRequest.Key
		cs.cache.Set(r.SetRequest.Key, gogocache.ByteView{Bytes: r.SetRequest.Value})
		rsp.RetCode = 1
		rsp.ResponseType = 2
	}
	return &rsp
}

//React 业务逻辑处理
func (cs *CacheServer) React(frame []byte, c gnet.Conn) (out []byte, action gnet.Action) {

	if cs.async {
		var r pb.Request
		err := proto.Unmarshal(frame, &r)

		if err != nil {
			proto.Marshal(&pb.Response{
				RetCode:    2,
				RetMessage: err.Error(),
			})
			return
		}
		_ = cs.workerPool.Submit(func() {
			res := cs.exec(&r)
			marshal, _ := proto.Marshal(res)
			c.AsyncWrite(marshal)
		})
		return
	}
	var r pb.Request
	err := proto.Unmarshal(frame, &r)

	if err != nil {
		proto.Marshal(&pb.Response{
			RetCode:    2,
			RetMessage: err.Error(),
		})
		return
	}
	res := cs.exec(&r)
	marshal, _ := proto.Marshal(res)
	out = marshal
	return
}

//NewServe 设置服务器参数
func NewServe(addr string, multicore, async bool, codec gnet.ICodec, cacheBytes int64, getter gogocache.Getter, cacheNum int, fn gogocache.Hash) {
	var err error
	if codec == nil {
		encoderConfig := gnet.EncoderConfig{
			ByteOrder:                       binary.BigEndian,
			LengthFieldLength:               4,
			LengthAdjustment:                0,
			LengthIncludesLengthFieldLength: false,
		}
		decoderConfig := gnet.DecoderConfig{
			ByteOrder:           binary.BigEndian,
			LengthFieldOffset:   0,
			LengthFieldLength:   4,
			LengthAdjustment:    0,
			InitialBytesToStrip: 4,
		}
		codec = gnet.NewLengthFieldBasedFrameCodec(encoderConfig, decoderConfig)
	}
	cs := &CacheServer{addr: addr, multicore: multicore, async: async, codec: codec,
		workerPool: goroutine.Default(), cache: gogocache.NewGroup(cacheBytes, getter, cacheNum, fn)}
	err = gnet.Serve(cs, addr, gnet.WithMulticore(multicore), gnet.WithTCPKeepAlive(time.Minute*5), gnet.WithCodec(codec))
	if err != nil {
		panic(err)
	}
}

//GetOutBoundIP 获取本机外网地址
func GetOutBoundIP() (ip string, err error) {
	conn, err := net.Dial("udp", "8.8.8.8:53")
	if err != nil {
		fmt.Println(err)
		return
	}
	localAddr := conn.LocalAddr().(*net.UDPAddr)
	fmt.Println(localAddr.String())
	ip = strings.Split(localAddr.String(), ":")[0]
	return
}

var (
	registryAddress string
	registryPath    string
)

func main() {
	var port int
	var multicore bool

	// Example command: go run server.go --port 9000 --multicore true
	flag.IntVar(&port, "port", 9000, "server port")
	flag.BoolVar(&multicore, "multicore", true, "multicore")
	flag.StringVar(&registryAddress, "registryAddress", "118.25.250.196", "registry address")
	flag.StringVar(&registryPath, "registryPath", "/test", "registry path")
	flag.Parse()
	ip, err := GetOutBoundIP()
	if err != nil {
		log.Fatal(err)
		return
	}
	addr := fmt.Sprintf("tcp://%s:%d", ip, port)
	NewServe(addr, true, false, nil, 2<<10, nil, 16, nil)
}
